package com.google.code.instrumentation.logging;

import javassist.CannotCompileException;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;

import org.apache.log4j.Logger;

import com.google.code.instrumentation.util.CompileTimeClassUtil;

public class Log4jMethodInstrumentor extends AbstractMethodInstrumentor
		implements LoggingMethodInstrumentor {
	public Log4jMethodInstrumentor(final CtMethod method) {
		super(method);
	}

	@Override
	public void addLogStart() throws CannotCompileException {
		try {
			final CtClass log4JClass = CompileTimeClassUtil
					.getCtClassFor(Logger.class);
			ctMethod.addLocalVariable("log", log4JClass);
			final CtClass stringClass = CompileTimeClassUtil
					.getCtClassFor(String.class);
			ctMethod.addLocalVariable("methodName", stringClass);

			final CtClass longClass = CompileTimeClassUtil
					.getCtClassFor(long.class);
			ctMethod.addLocalVariable("startTime", longClass);

			final String methodName = ctMethod.getName();

			// subsequent insert statements mean that the final statement
			// order is in reverse

			// TODO - surround with if( log.isTraceEnabled ){ }
			// if statement does not compile
			ctMethod.insertBefore("log.trace(String.format(\"ENTRY: ["
					+ methodName
					+ "(%s)]\", new Object[] {java.util.Arrays.deepToString($args)}));");
			ctMethod.insertBefore("log = org.apache.log4j.Logger.getLogger(this.getClass().getName());");
			ctMethod.insertBefore("methodName = \"" + methodName + "\";");
			ctMethod.insertBefore("startTime = System.currentTimeMillis();");

		} catch (final NotFoundException e) {
			throw new CannotCompileException(e);
		}

	}

	@Override
	public void addLogEnd() throws CannotCompileException {
		final String methodName = ctMethod.getName();
		final boolean asFinally = true;

		final String logEndMessageStmt = "String.format(\"EXIT: [%s]\", new Object[]{\""
				+ methodName + "\"})";

		try {
			ctMethod.addLocalVariable("logEndMsg",
					CompileTimeClassUtil.getCtClassFor(String.class));
			ctMethod.addLocalVariable("elapsedTime",
					CompileTimeClassUtil.getCtClassFor(long.class));
		} catch (final NotFoundException e) {
			// Should not happen
			throw new RuntimeException("Failed to find JRE class", e);
		}

		ctMethod.insertBefore("logEndMsg = " + logEndMessageStmt + ";");

		// We don't have access to the local variable. So just output the log message twice for now!
		ctMethod.insertAfter("elapsedTime = System.currentTimeMillis() - startTime;");
		ctMethod.insertAfter("log.trace(logEndMsg + \" ElapsedTime: \"+elapsedTime+\"ms\");");

		// We have to get hold of the logger again, because the local variable is not available in the finally block
		// :(
		ctMethod.insertAfter(
				String.format(
						"org.apache.log4j.Logger.getLogger(this.getClass().getName()).trace(%s);",
						logEndMessageStmt), asFinally);

	}
}